import os, sys
import socket
import struct
import signal
from system import CenturalAC, UserFacade, ReceptionFacade, Singleton, manager_get_report
import threading
from fractions import Fraction

socket_list: 'list[socket.socket]' = []
def term_handler(signalnum, frame):
    global socket_list
    for s in socket_list:
        try:
            name = s.getpeername()
            s.shutdown(socket.SHUT_RDWR)
            s.close()
            print('terminate:', name)
        except:
            continue
    sys.exit()

def recv_file(sock: socket.socket):
    fileinfo_size = struct.calcsize('!128sl')
    buf = sock.recv(fileinfo_size)
    if buf:
        filename, filesize = struct.unpack('!128sl', buf)
        fn = filename.strip(b'\00').decode('utf-8')
        print('file new name is {0}, filesize if {1}'.format(str(fn), filesize))
        recvd_size = 0
        file_path = 'recv_' + str(fn)
        fp = open(file_path, 'wb')
        print('start receiving...')
        while not recvd_size == filesize:
            if filesize - recvd_size > 1024:
                data = sock.recv(1024)
                recvd_size += len(data)
            else:
                data = sock.recv(filesize - recvd_size)
                recvd_size = filesize
            fp.write(data)
        fp.close()
        print('end receive...')
        return file_path
    else:
        print('failed to recv file')
        return False

def send_file(filepath: str, sock: socket.socket):
    try:
        if os.path.isfile(filepath):
            fileinfo_size = struct.calcsize('!128sl')  # 长128的str, 1个long
            fhead = struct.pack('!128sl', os.path.basename(filepath).encode('utf-8'),
                                os.stat(filepath).st_size)
            sock.send(fhead)
            fp = open(filepath, 'rb')
            while 1:
                data = fp.read(1024)
                if not data:
                    print('{0} file sent'.format(os.path.basename(filepath)))
                    break
                sock.send(data)
            return True
        else:
            print("Invalid filename")
            return False
    except Exception as e:
        print("Error when sending file: " + str(e))
        return False

class Server(object, metaclass=Singleton):
    def __init__(self, port: int = 8000):
        self.address = ('', port)
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.address)
        self.socket.listen(32)
        signal.signal(signal.SIGTERM, term_handler)
        global socket_list
        socket_list.append(self.socket)

    def _threadfunc(self, sock: socket.socket, addr):
        """
        根据接收到的第一句话判断客户端身份
        并交给不同的处理函数(检查空调系统状态, 不对则拒绝)
        顾客 前台 经理 管理 0-3
        """
        data = sock.recv(1024)
        data = struct.unpack('!iii', data)
        ac = CenturalAC()  # self.the_ac

        if data[0] == 0:
            if ac.is_working.is_set():
                self._user_handler(sock, room_id=int(data[1]), room_temp=float(data[2]))
            else:
                self.send_msg("Centural AC not started", sock)
                sock.shutdown(socket.SHUT_RDWR)
                sock.close()
        elif data[0] == 1:
            print('recp req get', ac.is_working.is_set())

            if ac.is_working.is_set():
                self._recp_handler(sock)
            else:
                self.send_msg("Centural AC not started", sock)
                sock.shutdown(socket.SHUT_RDWR)
                sock.close()
        elif data[0] == 2:
            if ac.is_working.is_set():
                self._mana_handler(sock)
            else:
                self.send_msg("Centural AC not started", sock)
                sock.shutdown(socket.SHUT_RDWR)
                sock.close()
        elif data[0] == 3:
            self._engi_handler(sock)
        else:
            print("Unexpected Message: {}".format(data))

    def _user_handler(self, sock: socket.socket, room_id: int, room_temp: float):
        """
        处理函数为管理员、用户、前台、经理的状态机
        ['stop', 'run', 'wait', 'pause']
        """
        try:
            recp_faca = ReceptionFacade([])
            print('validating room id: ', room_id in recp_faca.room_stat.keys() and not recp_faca.room_stat[room_id])
            if room_id in recp_faca.room_stat.keys() and not recp_faca.room_stat[room_id]:
                ac = CenturalAC()
                mode = int(ac.cool_warm)
                print(mode, int(ac.def_temp), ac.fan_speed,
                                   ac.fee_rate[1], ac.fee_rate[2], ac.fee_rate[3],
                                   ac.temp_upper, ac.temp_lower)
                data = struct.pack('!iiifffff', mode, int(ac.def_temp), ac.fan_speed,
                                   ac.fee_rate[1], ac.fee_rate[2], ac.fee_rate[3],
                                   ac.temp_upper, ac.temp_lower)
                sock.send(data)
            else: # 房号不存在or未入住
                sock.send(struct.pack('!iiifffff', -1, -1, -1, -1, -1, -1, -1, -1))
                print('invalid room id, client disconnect from server')
                return
            is_open = False
            user_facade = UserFacade(0, 0, [])
            while ac.is_working.is_set():
                data = sock.recv(8)
                if data:  # 断开时返回 b'' (空包)
                    op, arg = struct.unpack('!ii', data)
                    if op == 0 and not is_open:
                        user_facade.power_on(room_id)
                        user_facade.change_room_temp(room_id, room_temp)
                        is_open = True
                        print('room mode and fee', user_facade.mode_str.index(ac.get_mode(room_id)), ac.get_fee_sum(room_id))
                        mode_fee = struct.pack('!if', user_facade.mode_str.index(ac.get_mode(room_id)),
                                               float(ac.get_fee_sum(room_id)))
                        sock.send(mode_fee)
                    elif op == 1 and is_open == True:
                        # client 已经保证来信合法性
                        user_facade.change_fan_speed(room_id, arg)
                    elif op == 2 and is_open == True:
                        user_facade.change_target_temp(room_id, arg)
                    elif op == 3:
                        data = user_facade.request_state(room_id)
                        status = struct.pack('!ifff', *data)
                        sock.send(status)
                    elif op == 4:
                        op, arg = struct.unpack('!if', data)
                        print('temp from user:', arg)
                        user_facade.change_room_temp(room_id, arg)
                    elif op == 5 and is_open == True:
                        user_facade.power_off(room_id)
                        is_open = False
                    else:
                        print("Unexpected op from room {}: {} {}".format(room_id, op, arg))
                else:
                    break
        except Exception as e:
            print("Exception: %s" % str(e))
        finally:
            ac = CenturalAC()
            ac.poweroff_ac(room_id, True)
            sock.shutdown(socket.SHUT_RDWR)
            sock.close()
            print('client disconnect from server')


    def _recp_handler(self, sock: socket.socket):
        """
        前台处理函数
        返回详单时返回json
        """
        try:
            the_ac = CenturalAC()
            rf = ReceptionFacade([])
            print(rf.room_stat)
            self.send_msg("Success", sock)
            print('enter recp handler', the_ac.is_working.is_set())
            while the_ac.is_working.is_set():
                data = sock.recv(1024)
                print('recp data received')
                if data:
                    op, arg = struct.unpack('!ii', data)
                    print('recption: ', op, arg)
                    if op == 0:
                        data = rf.checkin()
                        print('assigned ', + data)
                        sock.send(struct.pack('!i', data))
                    elif op == 1:
                        data = rf.get_bill(arg)
                        print('bill ', data, arg)
                        if data:
                            to_sent = struct.pack('!fff', *data)
                            sock.send(to_sent)
                        else:
                            to_sent = struct.pack('!fff', -1, -1, -1)
                            sock.send(to_sent)
                    elif op == 2:
                        data_str = rf.get_rd(arg)
                        if data_str:  # 合法
                            fmt_str = '!i' + str(len(data_str)) + 's'
                            sock.send(struct.pack(fmt_str, len(data_str), data_str.encode()))
                        else:
                            sock.send(struct.pack('!i', 0))
                    elif op == 3:
                        data = rf.checkout(arg)
                        print(data, 'checkout')
                        if data:
                            sock.send(struct.pack('!i', 1))
                        else:
                            sock.send(struct.pack('!i', 0))
                else:
                    break
        except Exception as e:
            print("Exception: %s" % str(e))
        finally:
            sock.shutdown(socket.SHUT_RDWR)
            sock.close()
            print('recption disconnect from server')

    def _engi_handler(self, sock: socket.socket):
        """
        管理员处理函数
        """
        try:
            the_ac = CenturalAC()
            para_set = 1
            if the_ac.temp_lower == 0:
                para_set = 0
            is_started = struct.pack('!ii', int(the_ac.is_working.is_set()), para_set)
            sock.send(is_started)
            para_setted = False
            while not the_ac.is_working.is_set():  # 未启动则设置参数
                data = sock.recv(1024)  # 收opcode
                if data:  # 若data有内容
                    op, arg = struct.unpack_from('!ii', data, 0)
                    if op == 0:
                        paras = struct.unpack_from('!ififffff', data, struct.calcsize('!ii'))
                        sock.send('received'.encode('utf-8'))  # 分开参数与文件信息
                        filepath = recv_file(sock)
                        room_list = []
                        try:
                            with open(filepath, 'rt') as recvd_file:
                                for line in recvd_file:
                                    line = line.strip('\n')
                                    if len(line):
                                        room_list.append(int(line))
                        except:
                            print('Invalid roomlist')
                            sock.send(struct.pack('!i', 0))
                            continue
                        os.remove(filepath)
                        print('def temp:', paras[1])
                        para_setted = the_ac.set_para(cool_warm=not bool(paras[0]), def_temp=paras[1],
                                        def_fan_speed=paras[2], temp_lower=paras[3], temp_upper=paras[4],
                                        fee_rate_l=Fraction(1, int(paras[5])), fee_rate_m=Fraction(1, int(paras[6])),
                                        fee_rate_h=Fraction(1, int(paras[7])), room_list=room_list
                                        )
                        if para_setted:
                            print('parameters set')
                            sock.send(struct.pack('!i', 1))
                        else:
                            print('Failed to set parameters')
                            sock.send(struct.pack('!i', 0))
                    elif op == 1:
                        if para_setted:  # 设置过参数才能startup
                            the_ac.startup()
                            sock.send(struct.pack('!i', 1))
                        else:
                            print('Start up without setting parameter')
                            sock.send(struct.pack('!i', 0))
                else:
                    break
            while the_ac.is_working.is_set():
                data = sock.recv(1024)  # 收opcode
                if data:
                    op, arg = struct.unpack('!ii', data)
                    if op == 2:
                        stat = the_ac.get_surveillance_stat(arg)
                        stat_pack = struct.pack('!iiffifff', *stat)
                        sock.send(stat_pack)
                    elif op == 3:
                        the_ac.shutdown()
                        # under test TODO
                else:
                    break
        except Exception as e:
            print("Exception: %s" % str(e))
        finally:
            sock.shutdown(socket.SHUT_RDWR)
            sock.close()
            print('engineer disconnect from server')

    def _mana_handler(self, sock: socket.socket):
        """
        经理处理函数
        """
        try:
            self.send_msg('Connected', sock)
            the_ac = CenturalAC()
            while the_ac.is_working.is_set():
                data = sock.recv(1024)
                if data:
                    op, arg = struct.unpack('!ii', data)
                    print('op from manager:', op, arg)
                    if op == 0:
                        if arg in range(0, 2):
                            report_path = manager_get_report(arg)
                            send_file(report_path, sock)
                        else:
                            print('invalid report type')
                else:
                    break
        except Exception as e:
            print("Exception in mamager handler: %s" % str(e))
        finally:
            sock.shutdown(socket.SHUT_RDWR)
            sock.close()

    def send_msg(self, msg: str, sock: socket):
        try:
            sock.send(msg.encode('utf-8'))
            return True
        except Exception as e:
            print("Error when sending msg: " + str(e))
            return False



    def run_func(self):
        global socket_list
        while True:
            conn, addr = self.socket.accept()
            t = threading.Thread(target=self._threadfunc, args=(conn, addr))
            socket_list.append(conn)
            t.start()

    def run(self):
        t = threading.Thread(target=self.run_func, args=(), daemon=True)
        t.start()

    def shutdown(self):
        self.socket.close()


if __name__ == '__main__':
    serv = Server(port=8000)
    serv.run()
    print('server started')
